{
 "cells": [
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:09.539966Z",
     "start_time": "2025-01-17T15:47:05.980991Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 2
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 准备数据"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:09.604090Z",
     "start_time": "2025-01-17T15:47:09.540990Z"
    }
   },
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing()\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:09.609763Z",
     "start_time": "2025-01-17T15:47:09.605090Z"
    }
   },
   "source": [
    "# print(housing.data[0:5])\n",
    "import pprint  #打印的格式比较 好看\n",
    "\n",
    "pprint.pprint(housing.data[0:5])\n",
    "print('-'*50)\n",
    "pprint.pprint(housing.target[0:5])"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "array([[ 8.32520000e+00,  4.10000000e+01,  6.98412698e+00,\n",
      "         1.02380952e+00,  3.22000000e+02,  2.55555556e+00,\n",
      "         3.78800000e+01, -1.22230000e+02],\n",
      "       [ 8.30140000e+00,  2.10000000e+01,  6.23813708e+00,\n",
      "         9.71880492e-01,  2.40100000e+03,  2.10984183e+00,\n",
      "         3.78600000e+01, -1.22220000e+02],\n",
      "       [ 7.25740000e+00,  5.20000000e+01,  8.28813559e+00,\n",
      "         1.07344633e+00,  4.96000000e+02,  2.80225989e+00,\n",
      "         3.78500000e+01, -1.22240000e+02],\n",
      "       [ 5.64310000e+00,  5.20000000e+01,  5.81735160e+00,\n",
      "         1.07305936e+00,  5.58000000e+02,  2.54794521e+00,\n",
      "         3.78500000e+01, -1.22250000e+02],\n",
      "       [ 3.84620000e+00,  5.20000000e+01,  6.28185328e+00,\n",
      "         1.08108108e+00,  5.65000000e+02,  2.18146718e+00,\n",
      "         3.78500000e+01, -1.22250000e+02]])\n",
      "--------------------------------------------------\n",
      "array([4.526, 3.585, 3.521, 3.413, 3.422])\n"
     ]
    }
   ],
   "execution_count": 4
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:09.650561Z",
     "start_time": "2025-01-17T15:47:09.610784Z"
    }
   },
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state = 7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state = 11)\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train],\n",
    "    \"valid\": [x_valid, y_valid],\n",
    "    \"test\": [x_test, y_test],\n",
    "}\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 5
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:09.659900Z",
     "start_time": "2025-01-17T15:47:09.651560Z"
    }
   },
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "scaler = StandardScaler()\n",
    "scaler.fit(x_train)"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-1 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-1 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-1 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-1 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-1 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-1 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 6
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 构建数据集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:09.668181Z",
     "start_time": "2025-01-17T15:47:09.660900Z"
    }
   },
   "source": [
    "from torch.utils.data import Dataset\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1)\n",
    "            \n",
    "    def __len__(self):\n",
    "        return len(self.x)\n",
    "    \n",
    "    def __getitem__(self, idx):\n",
    "        return self.x[idx], self.y[idx]\n",
    "    \n",
    "    \n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "outputs": [],
   "execution_count": 7
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:09.675822Z",
     "start_time": "2025-01-17T15:47:09.669181Z"
    }
   },
   "source": [
    "train_ds[1]"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([-0.2981,  0.3523, -0.1092, -0.2506, -0.0341, -0.0060,  1.0806, -1.0611]),\n",
       " tensor([1.5140]))"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 8
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:09.681070Z",
     "start_time": "2025-01-17T15:47:09.676822Z"
    }
   },
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "batch_size = 256\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "outputs": [],
   "execution_count": 9
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "自定义 Layer"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:09.686949Z",
     "start_time": "2025-01-17T15:47:09.682069Z"
    }
   },
   "source": [
    "class CustomizedLinear(nn.Module):\n",
    "    def __init__(self, in_features, out_features):\n",
    "        super().__init__()\n",
    "        # 感兴趣的可以在这里实现其他的初始化方法\n",
    "        self.weights = nn.Parameter(torch.zeros(in_features, out_features)) # 定义权重\n",
    "        self.weights = nn.init.xavier_normal_(self.weights) # 使用xavier_normal初始化\n",
    "        self.bias = nn.Parameter(torch.zeros(out_features))  #Parameter是nn.Module的子类，可以被自动求导，可以被优化器优化\n",
    "        \n",
    "    def forward(self, x):\n",
    "        return torch.mm(x, self.weights) + self.bias # 全连接层的前向计算,mm是矩阵乘法"
   ],
   "outputs": [],
   "execution_count": 10
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:09.694457Z",
     "start_time": "2025-01-17T15:47:09.689942Z"
    }
   },
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            CustomizedLinear(input_dim, 30),\n",
    "            nn.ReLU(),\n",
    "            CustomizedLinear(30, 1)\n",
    "            )\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 8]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ],
   "outputs": [],
   "execution_count": 11
  },
  {
   "cell_type": "code",
   "source": [
    "model = NeuralNetwork()\n",
    "for name, param in model.named_parameters():\n",
    "    print(name, param.shape)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:09.762804Z",
     "start_time": "2025-01-17T15:47:09.696458Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "linear_relu_stack.0.weights torch.Size([8, 30])\n",
      "linear_relu_stack.0.bias torch.Size([30])\n",
      "linear_relu_stack.2.weights torch.Size([30, 1])\n",
      "linear_relu_stack.2.bias torch.Size([1])\n"
     ]
    }
   ],
   "execution_count": 12
  },
  {
   "cell_type": "code",
   "source": [
    "for m in model.modules():\n",
    "    print(m)\n",
    "    print('-'*50)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:57.550572Z",
     "start_time": "2025-01-17T15:47:57.546655Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NeuralNetwork(\n",
      "  (linear_relu_stack): Sequential(\n",
      "    (0): CustomizedLinear()\n",
      "    (1): ReLU()\n",
      "    (2): CustomizedLinear()\n",
      "  )\n",
      ")\n",
      "--------------------------------------------------\n",
      "Sequential(\n",
      "  (0): CustomizedLinear()\n",
      "  (1): ReLU()\n",
      "  (2): CustomizedLinear()\n",
      ")\n",
      "--------------------------------------------------\n",
      "CustomizedLinear()\n",
      "--------------------------------------------------\n",
      "ReLU()\n",
      "--------------------------------------------------\n",
      "CustomizedLinear()\n",
      "--------------------------------------------------\n"
     ]
    }
   ],
   "execution_count": 19
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:09.773174Z",
     "start_time": "2025-01-17T15:47:09.768187Z"
    }
   },
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "outputs": [],
   "execution_count": 14
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:09.778634Z",
     "start_time": "2025-01-17T15:47:09.774677Z"
    }
   },
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "    return np.mean(loss_list)\n"
   ],
   "outputs": [],
   "execution_count": 15
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:28.000898Z",
     "start_time": "2025-01-17T15:47:09.779633Z"
    }
   },
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    " \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/4600 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "a4b88b8b30cb46e7a9ce774ae75b7008"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 16
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:28.107137Z",
     "start_time": "2025-01-17T15:47:28.001899Z"
    }
   },
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "plot_learning_curves(record)  #横坐标是 steps"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAhYAAAGwCAYAAAD16iy9AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAARXZJREFUeJzt3Ql4XHW9//HvbNmTpkm6UJruyE6hZZFFRAtlE8ENL/BHQdQrwhVEuYhe0aJQROWKqOiVK7ghKJeistkKlFKg0IJQylKgTfc1bdPsyWTm/J/v78yZzCRpyHJm5uTM+/U855ntZOZkTpL55PvbApZlWQIAAOCCoBtPAgAAoAgWAADANQQLAADgGoIFAABwDcECAAC4hmABAABcQ7AAAACuCUuWxeNx2bJli5SXl0sgEMj2ywMAgCHQaa+amppkwoQJEgwGvRMsNFTU1tZm+2UBAIALNm7cKBMnTvROsNBKhXNgFRUVrj1vNBqVhQsXyty5cyUSibj2vBg8zoV3cC68g3PhHZyLoWlsbDSFAedz3DPBwmn+0FDhdrAoKSkxz8kPSm5xLryDc+EdnAvv4FwMz3t1Y6DzJgAAcA3BAgAAuIZgAQAAXJP1PhYAAP/RqQQ6OztlpPSxCIfD0t7eLrFYLNeH4xna3yQUCg37eQgWAIBh0UBRV1dnwsVImY9h/PjxZnQi8ymlq6ysNO/NcN4XggUAYFgf0lu3bjX/6epQxP4mTvIKDUDNzc1SVlY2Io43W+extbVVduzYYW7vt99+Q34uggUAYMi6urrMB5LOxqhDOEdSs01RURHBIkVxcbG51HAxduzYITeL8I4CAIbM6aNQUFCQ60OBC5xwqP1QhopgAQAYNvoq+EPAhfNIsAAAAK4hWAAAANcQLAAAGIYpU6bIT37yE1eea/HixaY5oqGhQUYq34wKae3skk0tIrG4JSwpAwDoz0c+8hGZPXu23H777cN+ruXLl0tpaakrx+UHvggW8bglx92yWNqjYTljTptMH0fvZADA8OZ10BEvOkPnexkzZkxWjmmk8EVTSDAYkEmj7SEydfUtuT4cAMjviZY6u3Ky6WsPxKWXXirPPvus/PSnPzXNDrrdc8895vKxxx4zlYzCwkJZunSprFmzRs4991wZN26cmVDrmGOOkX/+85/9NoUEAgG566675GMf+5gZvnnAAQfI3/72tyG/p//3f/8nhx56qDkmfa0f//jHaY//4he/MK+h83LocX7yk59MPvbAAw/I4YcfbuaoqK6ullNPPVVaWjL7OemLioWaUlMib+9olrpdrbk+FADIW23RmBxywz9y8tpv3Hi6lBS898eahoA333xTZs6cKd/73vfMfa+//rq5/MY3viE/+tGPZNq0aTJ69Ggz7fdZZ50lN910k/lg/93vfifnnHOOrF69WiZNmrTP15g3b57ceuut8sMf/lDuuOMOueiii2T9+vVSVVU1qO/ppZdekvPPP1+++93vyqc//Wl57rnn5Mtf/rIJCZdccomsWLFCvvKVr8jvf/97OeGEE2T37t3yzDPPmK/VGVEvuOACcxwacpqamsxjAw1gku/BYlqN3b5FxQIA0J9Ro0aZCb20mqDrYqi33nrLXN54441y2mmnJffVIKABxKFBZMGCBaYCceWVV+7zNS655BLzoa5uvvlmUx158cUX5YwzzhjUsd52220yZ84c+fa3v21uv+9975M33njDBBZ9jQ0bNpj+HdpnpLy8XCZPnixHHXVUMljozKgf//jHzf1KqxeZ5ptgMbXGaQqhYgEAuVIcCZnKQa5ee7iOPvrotNu6pohWCx555JHkB3VbW5v5QO/PEUcckbyuH/wVFRXJdTgGQysr2hST6sQTTzRVF+0DoiFIQ4NWWDS06OY0wWgg0lCiYeL000+XuXPnmmYSrcRkki/6WKipiYrFWioWAJAz2r9AmyNysbkxa2TP0R1f//rXTYVCqw7ajPDKK6+YD+r3WiI+Ekkfn6jHlonVX7VK8fLLL8uf/vQns3DYDTfcYAKFDlfVtT4WLVpk+o0ccsghpknmwAMPNCvRZlLQb00h2xs7pKWjK9eHAwDwMG0KcdY56Y928tQmB60CaKDQppN169ZJthx88MHmGHoekzaJOIuE6cgV7ZSpfSlWrlxpju/JJ59MBhqtcGifj3/961/m+9aglEm+aQoZVRyRsrAlzV0B08/isP1H5fqQAAAepR0vtc+DfgjraI99VRN0tMWDDz5oOmzqh7T2dchE5WFfvva1r5mRKNq3QztvPv/88/Kzn/3MjARRDz/8sKxdu1ZOPvlk08Tx6KOPmuPTysQLL7wgTzzxhGkC0dVK9fbOnTtNWPFMxULbmZyhOc520EEHiVeMtVd8lTU7m3N9KAAAD9OOl/ofvzYR6DwU++ozoZ0n9QNbR1xouNC+CrNmzcracc6aNUv+/Oc/y3333SeHHXaYaerQDqZaRVGVlZUm+Hz4wx82geGXv/ylaRbR4anar2PJkiVmVItWOP7rv/7LDFU988wzvVWx0INNHcM7kMlDsmVMkSVrm+yKBQAA+zJjxgzTpBAMdv9/7XxYp9J5I5xmBccVV1yRdrtn04jVx3DOgU7Rfcopp/T6+k984hNm68tJJ51kpgHviwaNxx9/XLJt0KlAg4QzPMdrxhXbJ2PtToIFAAC5MOhg8c4778iECRPMDF/HH3+8zJ8/v99JQjo6OszmaGxsNJfRaNRsbtHn6m4KaXL1uTE4znvPOcg9zoV3+PVc6Pej/2Fru342+x4Mh1MRcI47my6//HL54x//2OdjOonWnXfeKbmk74e+L3penc6hjoH+7AasQUzBpUNWdEyvdgrR8bzay3Tz5s2yatUqM+RlX/0ydL+e7r33XjPO1k3b20RufiUshUFLfnBsTFwYeQQAGEAVu7a21ow4QP+086TOgNkX/RzN9bojOoxWZxvdtm2bmbMjVWtrq1x44YWyd+9e03/DlWDRV5uRTsyhnVsuu+yyAVcs9Aewvr6+3wMbrOCfzpfGrWvlEw1XyVprnDxz7ckyvqLItefHwGmq1bHTOnFLz7HcyC7OhXf49Vy0t7ebDyLti6CV7JFAP/b0w10/yN2Y+8JP2tvbTZ8R/ZzueT7187umpuY9g8Wwel5qb1Ttafruu+/ucx+dW123nvQXy81fLmvHGzK6bZtMG2XJ2gaRjXs6pLa67yoKssPtc4yh41x4h9/Ohc4FoR/O2gkytSOklznNH85xo5u+H/q+9PVzOtCf22G9o9osoiu/6WxfORe0M1JtpV2KW8PIEAAAsm5QwUKnNn366adNmURXWNOZyLRzh7PQiheCxcRRdqKqY2QIAABZN6imkE2bNpkQsWvXLtPBRMfPLlu2LOedTYyg3Xt1/wq7YrG2nkmyAADwdLDQmb88K1Gx2L/CDhjMZQEAQPb5p9dK0G4CmVBuX27a0yodXe+9wAwAAIOlo2B06fKBCAQC8tBDD0m+8E2wsBJNIRWFIuWFYYlbIut3teb6sAAAyCu+CRZOU0ggHpNpY+wl1NeyGBkAAFnlu2AhJliUmatrGXIKANmlcy52tuRmG+B8j//zP/9jFujqOZ33ueeeK5/73OfMNAp6fdy4cWZJdV22PHXxzeF67bXXzGqkxcXFUl1dLV/84hfN9A0OXVTs2GOPldLSUjNf1Iknnijr1683j7366qvyoQ99yEzupZNUzZ49W1asWCFe4p2lSYcr0RQiVpdMrXEqFgQLAMiqaKvIzRNy89rf3CJSYP/978+nPvUpueqqq+Spp54yM6Gq3bt3m5VAH330UfMhr0uN33TTTWaCx9/97ndmyfTVq1f3uzbWQLS0tJil13WtreXLl8uOHTvk85//vFnG/Z577jHTaJ933nnyhS98wSx/rlNsv/jii8kZQnU9kaOOOsqsKaLTPbzyyiuem3DNR8Ei8a3EojSFAAD2afTo0XLqqaeaD24nWDzwwANmumqtBujskzNnzkzu/73vfU8WLFggf/vb30wAGI57773XTJutYUUrEupnP/uZCS4/+MEPTEjQKbM/8pGPyPTp083jWl1xbNiwQa699lo56KCDzO0DDjhAvCbsy6aQcTSFAEBORErsykGuXnuAtGpx9dVXm//8tSqhK47+27/9mwkVWrHQBTQfeeQRs+CmVhHa2trMh/pwvfnmmya0OKFCaVOHNstoReTkk0+WSy65xFQ1NPRoADr//POTM1xfc801psLx+9//3jym34cTQLzCh30suptCGlqjsrulM7fHBQD5REv22hyRi20QC4qdccYZZjEyDQ+6iNozzzxjmhmcWaa1QnHzzTeb+7W54fDDDzfNEtlw9913y/PPPy8nnHCC3H///WZNLp2MUmngef311+Xss8+WJ598Ug455BBzrF7iy2BRXBCS/SuLzU2aQwAAPenKnboshVYqtEnkwAMPlFmzZpnHnn32WVM10Mc1UOiy8LqUhRsOPvhg0wFT+1o49PW0UqLH4NB+FNdff71ZPuOwww4zTSgODRpf/epXZeHChfLxj3/cBBEv8eVwU9Xdz4LmEABAbxdeeKGpWPzmN79JViucfgsPPvigqVRoCND9eo4gGaqLLrrIhJrPfvazsmrVKtOB9D/+4z/k4osvNqNQ6urqTKDQioWOBNHw8M4775hAos0x2sdDR43oYxpItANoah8ML/DlqBA1raZUnnmnnn4WAIA+6ZDPqqoq07dBw4PjtttuM8NOtSlCO3Red9110tjY6MprlpSUyD/+8Q8zKkWHsertT3ziE+Y1ncffeust+e1v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iy4yE9BUVlbKZZddJs3NzWn7rFy5Uj7wgQ+Y86Yz4d16661Z+f5Gkvnz58sxxxxjZqsdO3asnHfeebJ69eq0fdrb2+WKK66Q6upqKSsrk0984hOyffv2tH02bNggZ599tpSUlJjnufbaa6Wrqyttn8WLF8usWbNMb/kZM2bIPffck5XvcaS488475YgjjkhOrHT88cfLY489lnyc85Abt9xyi/k7dfXVVyfv41zkkOUD9913n1VQUGD95je/sV5//XXrC1/4glVZWWlt374914c2Yj366KPWt771LevBBx/UUUPWggUL0h6/5ZZbrFGjRlkPPfSQ9eqrr1of/ehHralTp1ptbW3Jfc444wxr5syZ1rJly6xnnnnGmjFjhnXBBRckH9+7d681btw466KLLrJWrVpl/elPf7KKi4utX/3qV1n9Xr3u9NNPt+6++27zHr3yyivWWWedZU2aNMlqbm5O7vOlL33Jqq2ttZ544glrxYoV1vvf/37rhBNOSD7e1dVlHXbYYdapp55q/etf/zLnt6amxrr++uuT+6xdu9YqKSmxrrnmGuuNN96w7rjjDisUClmPP/541r9nr/rb3/5mPfLII9bbb79trV692vrmN79pRSIRc24U5yH7XnzxRWvKlCnWEUccYV111VXJ+zkXueOLYHHsscdaV1xxRfJ2LBazJkyYYM2fPz+nx+UXPYNFPB63xo8fb/3whz9M3tfQ0GAVFhaacKD0l1C/bvny5cl9HnvsMSsQCFibN282t3/xi19Yo0ePtjo6OpL7XHfdddaBBx6Ype9sZNqxY4d5b59++unke68fbn/5y1+S+7z55ptmn+eff97c1j+awWDQ2rZtW3KfO++806qoqEi+///5n/9pHXrooWmv9elPf9oEG+yb/gzfddddnIccaGpqsg444ABr0aJF1gc/+MFksOBc5NaIbwrp7OyUl156yZTiU9cj0dvPP/98To/Nr+rq6mTbtm1p77nOH69NUM57rpfa/HH00Ucn99H99dy88MILyX1OPvlkKSgoSO5z+umnmzL/nj17svo9jSR79+41l1VVVeZSf/51GejU83HQQQfJpEmT0s7H4YcfLuPGjUt7r3Uxptdffz25T+pzOPvwe9S3WCwm9913n7S0tJgmEc5D9mlThzZl9Hy/OBe5lfVFyNxWX19vfsFTfziU3n7rrbdy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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 17
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T15:47:28.139062Z",
     "start_time": "2025-01-17T15:47:28.108137Z"
    }
   },
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.3813\n"
     ]
    }
   ],
   "execution_count": 18
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
